/**
 * 
 */
package com.pi.tools;

//--------------------------------- PACKAGE ------------------------------------ 
//--------------------------------- IMPORTS ------------------------------------ 
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;


import java.io.File;
import java.io.RandomAccessFile;
import java.util.Hashtable;import com.mapdigit.gis.MapLayer;import com.mapdigit.gis.geometry.GeoLatLng;import com.mapdigit.gis.geometry.GeoPoint;

//[------------------------------ MAIN CLASS ----------------------------------] 

/**
 * The map in china has so-call offet, the laitude/longitude received by GPS
 * device is not actually mapped to the real position, it has "offset", this
 * class is used for amend such offset.
 */
public class LocalChinaMapOffset {

	RandomAccessFile mapOffsetDataFile = null;
	private static int RECORDCOUNT = 9813676;
	private static int RECORDLENGTH = 8;
	private static LocalChinaMapOffset localChinaMapOffset;
	private String chinamapoffset;

	private LocalChinaMapOffset(String chinamapoffset) {
		this.chinamapoffset = chinamapoffset;
	}

	public static synchronized LocalChinaMapOffset getInstance(
			String chinamapoffset) {
		if (localChinaMapOffset == null) {
			localChinaMapOffset = new LocalChinaMapOffset(chinamapoffset);
		}
		return localChinaMapOffset;
	}

	public void open(String filename) throws FileNotFoundException {
		mapOffsetDataFile = new RandomAccessFile(filename, "r");
	}

	public void open() throws FileNotFoundException, IOException {
		String path = (new java.io.File(".").getCanonicalPath())
				+ File.separator;
		mapOffsetDataFile = new RandomAccessFile(path + "chinamapoffset.dat",
				"r");
	}

	public void close() throws IOException {
		if (mapOffsetDataFile != null) {
			mapOffsetDataFile.close();
		}
	}

	// //////////////////////////////////////////////////////////////////////////
	// --------------------------------- REVISIONS
	// ------------------------------
	// Date Name Tracking # Description
	// --------- ------------------- ------------- ----------------------
	// 19FEB2010 James Shen Code review
	// //////////////////////////////////////////////////////////////////////////
	/**
	 * Get map offset at given location and level.
	 * 
	 * @param longitude
	 *            longitude of WGS location.
	 * @param latitude
	 *            latitude of WGS location.
	 * @param mapLevel
	 *            map Level, 0-18.
	 * @return offset in china.
	 */
	public GeoPoint getOffset(double longitude, double latitude, int mapLevel) {
		if (mapLevel < 11) {
			return new GeoPoint(0, 0);
		}
		GeoPoint queryPoint = getQueryLocation(latitude, longitude);
		String key = queryPoint.x + "|" + queryPoint.y;
		GeoPoint cachedPoint = (GeoPoint) offsetCache.get(key);
		if (cachedPoint == null) {
			GeoPoint pt = getOffsetFromServer(queryPoint.x / 100.0,
					queryPoint.y / 100.0);
			offsetCache.put(key, pt);
			cachedPoint = pt;
		}
		return new GeoPoint((int) cachedPoint.x >> (18 - mapLevel),
				(int) cachedPoint.y >> (18 - mapLevel));
	}

	// //////////////////////////////////////////////////////////////////////////
	// --------------------------------- REVISIONS
	// ------------------------------
	// Date Name Tracking # Description
	// --------- ------------------- ------------- ----------------------
	// 19FEB2010 James Shen Code review
	// //////////////////////////////////////////////////////////////////////////
	/**
	 * Convert coordinates from WGS to Mars(China)
	 * 
	 * @param earth
	 *            WGS lat/lng pair.
	 * @return Mars' coordinates lat/lng with deviation in China.
	 */
	public GeoLatLng fromEarthToMars(GeoLatLng earth) {
		GeoPoint ptOffset = getOffset(earth.x, earth.y, 11);
		if (ptOffset.x != 0 || ptOffset.y != 0) {
			GeoPoint pt = MapLayer.fromLatLngToPixel(earth, 11);
			pt.x += ptOffset.x;
			pt.y += ptOffset.y;
			return MapLayer.fromPixelToLatLng(pt, 11);

		} else {
			return new GeoLatLng(earth);
		}

	}

	public GeoLatLng fromEarthToMars(GeoPoint pt) {
		if (pt.x != 0 || pt.y != 0) {
			return MapLayer.fromPixelToLatLng(pt, 18);
		} else {
			return null;
		}
	}

	// //////////////////////////////////////////////////////////////////////////
	// --------------------------------- REVISIONS
	// ------------------------------
	// Date Name Tracking # Description
	// --------- ------------------- ------------- ----------------------
	// 19FEB2010 James Shen Code review
	// //////////////////////////////////////////////////////////////////////////
	/**
	 * Convert coordinates from Mars(China) to WGS
	 * 
	 * @param mar
	 *            lat/lng with deviation in China.
	 * @return WGS coordinates
	 */
	public GeoLatLng fromMarsToEarth(GeoLatLng mar) {
		GeoPoint ptOffset = getOffset(mar.x, mar.y, 18);
		if (ptOffset.x != 0 || ptOffset.y != 0) {
			GeoPoint pt = MapLayer.fromLatLngToPixel(mar, 18);
			pt.x -= ptOffset.x;
			pt.y -= ptOffset.y;
			return MapLayer.fromPixelToLatLng(pt, 18);

		} else {
			return new GeoLatLng(mar);
		}
	}

	// //////////////////////////////////////////////////////////////////////////
	// --------------------------------- REVISIONS
	// ------------------------------
	// Date Name Tracking # Description
	// --------- ------------------- ------------- ----------------------
	// 19FEB2010 James Shen Code review
	// //////////////////////////////////////////////////////////////////////////
	/**
	 * Set the offset query base url.
	 * 
	 * @param baseUrl
	 *            base url.
	 */
	public void setQueryBaseURL(String baseUrl) {
		QUERY_URL = baseUrl;
	}

	/**
	 * internal cache.
	 */
	private Hashtable offsetCache = new Hashtable(128);

	/**
	 * default sever url.
	 */
	private String QUERY_URL = "http://www.mapdigit.com/guidebeemap/offset.php?";

	// //////////////////////////////////////////////////////////////////////////
	// --------------------------------- REVISIONS
	// ------------------------------
	// Date Name Tracking # Description
	// --------- ------------------- ------------- ----------------------
	// 19FEB2010 James Shen Code review
	// //////////////////////////////////////////////////////////////////////////
	/**
	 * normalized latitude and longitude.
	 * 
	 * @param latitude
	 * @param longitude
	 * @return
	 */
	private GeoPoint getQueryLocation(double latitude, double longitude) {
		int lat = (int) (latitude * 100);
		int lng = (int) (longitude * 100);
		double lat1 = ((int) (latitude * 1000 + 0.499999)) / 10.0;
		double lng1 = ((int) (longitude * 1000 + 0.499999)) / 10.0;
		for (double x = longitude; x < longitude + 1; x += 0.5) {
			for (double y = latitude; x < latitude + 1; y += 0.5) {
				if (x <= lng1 && lng1 < (x + 0.5) && lat1 >= y
						&& lat1 < (y + 0.5)) {
					return new GeoPoint((int) (x + 0.5), (int) (y + 0.5));
				}
			}
		}
		return new GeoPoint(lng, lat);
	}

	private int readInt() throws IOException {
		int byte1 = mapOffsetDataFile.readUnsignedByte();
		int byte2 = mapOffsetDataFile.readUnsignedByte();
		return byte1 + ((int) byte2 << 8);
	}

	// //////////////////////////////////////////////////////////////////////////
	// --------------------------------- REVISIONS
	// ------------------------------
	// Date Name Tracking # Description
	// --------- ------------------- ------------- ----------------------
	// 19FEB2010 James Shen Code review
	// //////////////////////////////////////////////////////////////////////////
	/**
	 * Get the offset from the sever.
	 * 
	 * @param longitude
	 * @param latitude
	 * @return
	 */
	private GeoPoint getOffsetFromServer(double longitude, double latitude) {
		int left = 0;
		try {
			open(chinamapoffset);
			int right = RECORDCOUNT;
			int lat, lng, offsetX, offsetY;
			lat = (int) latitude * 100;
			lng = (int) longitude * 100;
			long queryValue = lat + ((long) lng << 16);
			while (left <= right) {
				int middle = (int) Math.floor((left + right) / 2.0);
				{
					mapOffsetDataFile.seek(middle * RECORDLENGTH);
					lng = readInt();
					lat = readInt();
					offsetX = readInt();
					offsetY = readInt();
					long middleValue = lat + ((long) lng << 16);
					if (queryValue == middleValue) {
						mapOffsetDataFile.seek(middle * RECORDLENGTH);
						return new GeoPoint(offsetX, offsetY);
					}
					if (queryValue > middleValue) {
						left = middle + 1;
					} else {
						right = middle - 1;
					}
				}
			}
			mapOffsetDataFile.close();
		} catch (Exception e) {
		}

		return new GeoPoint(0, 0);

	}

	public static void main(String[] args) {
		LocalChinaMapOffset chinaMapOffset = LocalChinaMapOffset
				.getInstance("C:\\Users\\DP\\Desktop\\googlepy\\chinamapoffset.dat");

		double lat = 23.14088567509316;
		double lng = 114.4281387324445;
		GeoPoint geoPoint = chinaMapOffset.getOffsetFromServer(lng, lat);
		GeoLatLng geoLatLng = chinaMapOffset.fromEarthToMars(geoPoint);
		System.out.println(geoLatLng.lat() + " " + geoLatLng.lng());
		GeoLatLng earth = new GeoLatLng(lat, lng);
		geoLatLng = chinaMapOffset.fromEarthToMars(earth);
		System.out.println(geoLatLng.lat() + " " + geoLatLng.lng());

	}
}
